/* eslint-disable react/prop-types */
import React from 'react'
import cssStyle from './index.module.css'
import PropTypes from 'prop-types'
import ReactDOM from 'react-dom'
import { Form } from '../index'

class Column extends React.PureComponent{
    render(){
        // eslint-disable-next-line no-unused-vars
        let { renderType, prop, align, width, render, lazy, type, checked, selectAll, label, ...rest } = this.props
        let Com = renderType
        return(
            <Com {...rest}>
                {
                    Com === 'th'
                    ?
                    (
                        type && type === 'selection'
                        ?
                        <span
                            className={ cssStyle.checkbox + ' ' + (checked === 0 ? cssStyle.checked : (checked === 1 ? cssStyle.indeterminate : '')) }
                            onClick={ selectAll }
                        >
                            <span className={ cssStyle.inner }></span>
                            <input type="checkbox" />
                        </span>
                        :
                        label
                    )
                    :
                    rest.children
                }
            </Com>
        )
    }
}

Column.propTypes = {
    renderType: PropTypes.string
}

class Table extends React.PureComponent{
    constructor(props){
        super(props)
        this.state = {
            itemWidth: 0,
            multipleSelectList: [],
            checked: 2, // 0:全选 1:部分选 2:未选
            expandList: []
        }
        this.tableRef = React.createRef()
        this.tbodyRef = React.createRef()
        this.resizeRef = React.createRef()
        this.colgroupRef = React.createRef()
        this.arriveResize = false
        this.startX = 0
        this.mouseDown = false
        this.tableWidthResize = this.tableWidthResize.bind(this)
        this.timer = null
        this.tableRefLeft = 0
        this.onMouseMove = this.onMouseMove.bind(this)
        this.onMouseDown = this.onMouseDown.bind(this)
        this.onMouseUp = this.onMouseUp.bind(this)
        this.distance = 0
        this.index = -1
        this.resizeRefLeft = 0
        this.colDom = null
        this.colDomNext = null
    }
    static getDerivedStateFromProps(nextProps, prevState){
        if (nextProps.data.length) {
            let num = 0
            nextProps.data.forEach(item => {
                const exist = prevState.multipleSelectList.some(_item => {
                    return _item[nextProps.rowKey] === item[nextProps.rowKey]
                })
                if (exist) {
                    num += 1
                }
            })
            if (num === nextProps.data.length) {
                prevState.checked = 0
            } else if (num > 0) {
                prevState.checked = 1
            } else {
                prevState.checked = 2
            }
        } else {
            prevState.checked = 2
        }
        return prevState
    }
    tableWidthResize() {
        let tableWidth = 0
        let noWidthColumnNum = 0
        React.Children.forEach(this.props.children, item => {
            if(item){
                if(item.props.width){
                    tableWidth += parseFloat(item.props.width)
                }else{
                    noWidthColumnNum++
                }
            }
        })
        let containerWidth = this.tableRef.current.offsetWidth
        if(noWidthColumnNum > 0){
            let itemWidth = 0
            if(tableWidth >= containerWidth){
                itemWidth = 30
            }else{
                let overWidth = containerWidth - tableWidth
                itemWidth = overWidth/noWidthColumnNum
            }
            this.setState({
                itemWidth
            })
        }
    }
    componentDidMount(){
        this.tableWidthResize()
        window.addEventListener('resize', this.tableWidthResize)
    }
    componentWillUnmount() {
        window.removeEventListener('resize', this.tableWidthResize)
    }
    componentDidUpdate() {
        if (this.state.expandList.length) {
            const deleteExpandRowkey = []
            this.state.expandList.forEach(item => {
                const unExist = this.props.data.findIndex(_item => {
                    return item == _item[this.props.rowKey]
                }) === -1
                if (unExist) {
                    deleteExpandRowkey.push(item)
                }
            })
            if (deleteExpandRowkey.length) {
                let children = Array.from(this.tbodyRef.current.children)
                const deleteExpandDom = []
                deleteExpandRowkey.forEach(item => {
                    deleteExpandDom.push(...children.filter(_item => {
                        return _item.dataset._rowkey == item
                    }))
                })
                deleteExpandDom.forEach(item => {
                    this.tbodyRef.current.removeChild(item)
                })
                this.setState(state => {
                    deleteExpandRowkey.forEach(item => {
                        const index = state.expandList.findIndex(_item => {
                            return item === _item
                        })
                        state.expandList.splice(index, 1)
                    })
                    return state
                })
            }
        }
    }
    selectionChange = item => {
        let multipleSelectList = [...this.state.multipleSelectList]
        let index
        for(let i = 0; i < multipleSelectList.length; i++){
            if(multipleSelectList[i][this.props.rowKey] === item[this.props.rowKey]){
                index = i
                break
            }
        }
        if(index !== undefined){
            multipleSelectList.splice(index, 1)
        }else{
            multipleSelectList.push(item)
        }
        this.setState({
            multipleSelectList
        })
        this.props.selectionChange(multipleSelectList)
    }
    selectAll = () => {
        if(this.state.multipleSelectList.length === this.props.data.length){
            this.setState({
                multipleSelectList: []
            })
            this.props.selectionChange([])
        }else{
            this.setState({
                multipleSelectList: [...this.props.data]
            })
            this.props.selectionChange([...this.props.data])
        }
    }
    expand = (f, item, lazy) => {
        let children = Array.from(this.tbodyRef.current.children)
        let currentElement = children.filter(_item => {
            return _item.dataset.rowkey == item[this.props.rowKey]
        })[0]
        if (currentElement) {
            const index = this.state.expandList.findIndex(_item => {
                return _item == item[this.props.rowKey]
            })
            if (index > -1) {
                this.setState(state => {
                    state.expandList.splice(index, 1)
                    return state
                })
                currentElement.removeAttribute("data-expand")
                const removeDom = children.filter(_item => {
                    return _item.dataset._rowkey == item[this.props.rowKey]
                })[0]
                if (removeDom) {
                    this.tbodyRef.current.removeChild(removeDom)
                }
            } else {
                if(lazy){
                    if(currentElement.dataset.lazy){
                        return
                    }
                    currentElement.setAttribute("data-lazy", true)
                    f(item).then(res => {
                        this.setState(state => {
                            state.expandList.push(item[this.props.rowKey])
                            return state
                        })
                        currentElement.setAttribute("data-expand", true)
                        let tr = document.createElement('tr')
                        tr.dataset._rowkey = item[this.props.rowKey]
                        let td = document.createElement('td')
                        ReactDOM.render(
                            res,
                            td
                        )
                        td.setAttribute("colspan", this.props.children.length)
                        tr.appendChild(td)
                        this.tbodyRef.current.insertBefore(tr, currentElement.nextElementSibling)
                    }).finally(() => {
                        currentElement.removeAttribute("data-lazy")
                    })
                }else{
                    currentElement.setAttribute("data-expand", true)
                    ReactDOM.render(
                        f(item),
                        td
                    )
                    tr.appendChild(td)
                    this.tbodyRef.current.insertBefore(tr, currentElement.nextElementSibling)
                }
            }
        }
    }
    clearSelection = () => {
        this.setState(state => {
            state.multipleSelectList.splice(0)
            return state
        })
    }
    clearExpand(){
        let children = Array.from(this.tbodyRef.current.children)
        if(children.length){
            let needRemoveChildren = []
            children.forEach(item => {
                if(item.dataset.rowkey === undefined){
                    needRemoveChildren.push(item)
                }
                if(item.dataset.expand !== undefined){
                    item.removeAttribute("data-expand")
                }
                if(item.dataset.lazy !== undefined){
                    item.removeAttribute("data-lazy")
                }
            })
            if(needRemoveChildren.length){
                needRemoveChildren.forEach(item => {
                    this.tbodyRef.current.removeChild(item)
                })
            }
        }
    }
    onMouseMove(e, index) {
        const currentTarget = e.currentTarget
        if (this.timer) {
            window.cancelAnimationFrame(this.timer)
        }
        this.timer = window.requestAnimationFrame(() => {
            const position = currentTarget.getBoundingClientRect()
            const left = e.clientX - position.left
            if (position.width - left <= 6) {
                document.body.style.cursor = 'col-resize'
                this.arriveResize = true
            } else if (!this.mouseDown) {
                document.body.style.cursor = 'auto'
                this.arriveResize = false
            }
            if (this.mouseDown && this.arriveResize) {
                const distance = e.clientX - this.startX
                const colDom = this.colgroupRef.current.getElementsByTagName('col')[index]
                if (distance < 0 && (distance + parseFloat(this.colDom.width) < 30 || this.colDom !== colDom)) {
                    this.distance = 30 - parseFloat(this.colDom.width)
                    return
                } else if (distance > 0 && this.colDomNext && (parseFloat(this.colDomNext.width) - distance) < 30) {
                    this.distance = parseFloat(this.colDomNext.width) - 30
                    return
                }
                this.distance = distance
                this.resizeRef.current.style.left = this.resizeRefLeft + this.distance + 'px'
            }
        })
    }
    onMouseDown(e, index) {
        if (this.arriveResize) {
            this.index = index
            this.colDom = this.colgroupRef.current.getElementsByTagName('col')[this.index]
            this.colDomNext = this.colgroupRef.current.getElementsByTagName('col')[this.index + 1]
            const currentTarget = e.currentTarget
            const position = currentTarget.getBoundingClientRect()
            this.mouseDown = true
            this.startX = e.clientX
            this.tableRefLeft = this.tableRef.current.getBoundingClientRect().left
            this.resizeRef.current.style.display = 'block'
            this.tableRef.current.style.userSelect = 'none'
            this.resizeRefLeft = position.left - this.tableRefLeft - 5 + currentTarget.offsetWidth
            this.resizeRef.current.style.left = this.resizeRefLeft + 'px'
            window.addEventListener('mouseup', this.onMouseUp)
        }
    }
    onMouseUp(e) {
        this.mouseDown = false
        this.arriveResize = false
        this.resizeRef.current.style.display = 'none'
        document.body.style.cursor = 'auto'
        this.tableRef.current.style.userSelect = 'unset'
        window.removeEventListener('mouseup', this.onMouseUp)
        
        const col1 = this.colgroupRef.current.getElementsByTagName('col')[this.index]
        const width1 = parseFloat(col1.width)
        col1.width = width1 + this.distance
        const col2 = this.colgroupRef.current.getElementsByTagName('col')[this.index + 1]
        if (col2) {
            const width2 = parseFloat(col2.width)
            col2.width = width2 - this.distance
        }
        this.index = -1
        this.distance = 0
    }
    render(){
        return(
            <div ref={ this.tableRef } className={ cssStyle.table } style={ this.props.style || {} }>
                <table style={{ width: '100%', minWidth: '100%', textAlign: this.props.align ? this.props.align : 'left' }}>
                    <colgroup ref={ this.colgroupRef }>
                        {
                            React.Children.map(this.props.children, (item, index) => {
                                console.log(item)
                                if(item){
                                    return(
                                        <col key={ index } width={ item.props.width ? item.props.width : this.state.itemWidth } />
                                    )
                                }
                            })
                        }
                    </colgroup>
                    <thead>
                        <tr>
                            {
                                React.Children.map(this.props.children, (item, index) => {
                                    if(item){
                                        if(item.props.type && item.props.type === 'selection'){
                                            return React.cloneElement(item, {
                                                renderType: 'th',
                                                style: {
                                                    textAlign: item.props.align ? item.props.align : (this.props.align ? this.props.align : 'left')
                                                },
                                                checked: this.state.checked,
                                                selectAll: this.selectAll,
                                                onMouseMove: e => {
                                                    this.onMouseMove(e, index)
                                                },
                                                onMouseDown: e => {
                                                    this.onMouseDown(e, index)
                                                }
                                            })
                                        }else{
                                            return React.cloneElement(item, {
                                                renderType: 'th',
                                                style: {
                                                    textAlign: item.props.align ? item.props.align : (this.props.align ? this.props.align : 'left')
                                                },
                                                onMouseMove: e => {
                                                    this.onMouseMove(e, index)
                                                },
                                                onMouseDown: e => {
                                                    this.onMouseDown(e, index)
                                                }
                                            })
                                        }
                                    }
                                })
                            }
                        </tr>
                    </thead>
                    <tbody className={ cssStyle.tbody } ref={ this.tbodyRef }>
                        {
                            this.props.data.length
                            ?
                            this.props.data.map((item, index) => {
                                let checked = false
                                for(let i = 0; i < this.state.multipleSelectList.length; i++){
                                    if(this.state.multipleSelectList[i][this.props.rowKey] === item[this.props.rowKey]){
                                        checked = true
                                        break
                                    }
                                }
                                return(
                                    <tr
                                        key={ item[this.props.rowKey] || index }
                                        data-rowkey={ item[this.props.rowKey] || index }
                                        className={ this.props.stripe ? cssStyle.stripe : '' }
                                    >
                                        {
                                            React.Children.map(this.props.children, _item => {
                                                if(_item){
                                                    let { type, align, prop, lazy, render } = _item.props
                                                    let Component = (
                                                        type === 'selection'
                                                        ?
                                                        <Form.checkbox checked={ checked } onChange={ () => { this.selectionChange(item) } } />
                                                        :
                                                        (
                                                            type === 'expand'
                                                            ?
                                                            <span className={ cssStyle.expand } onClick={ () => { this.expand(render, item, lazy) } }>&gt;</span>
                                                            :
                                                            (
                                                                prop
                                                                ?
                                                                item[prop]
                                                                :
                                                                (
                                                                    render
                                                                    ?
                                                                    <div style={{ display: 'inline-block'}}>
                                                                        {
                                                                            render(item, index)
                                                                        }
                                                                    </div>
                                                                    :
                                                                    ''
                                                                )
                                                            )
                                                        )
                                                    )
                                                    return React.cloneElement(_item, {
                                                        renderType: 'td',
                                                        style: {
                                                            textAlign: align ? align : (this.props.align ? this.props.align : 'left'),
                                                            wordBreak: 'break-all'
                                                        }
                                                    }, Component)
                                                }
                                            })
                                        }
                                    </tr>
                                )
                            })
                            :
                            <tr>
                                <td colSpan={ this.props.children.length }>
                                    <div className={ cssStyle.nodata }>暂无数据</div>
                                </td>
                            </tr>
                        }
                    </tbody>
                </table>
                {
                    this.props.loading
                    ?
                    <div className={ cssStyle.loadingMask }>
                        <div className={ cssStyle.loadingSpinner }>
                            <svg viewBox="25 25 50 50" className={ cssStyle.circular }>
                                <circle cx="50" cy="50" r="20" fill="none" className={ cssStyle.path }></circle>
                            </svg>
                        </div>
                    </div>
                    :
                    null
                }
                <div ref={ this.resizeRef } className={ cssStyle.resize }></div>
            </div>
        )
    }
}

Table.propTypes = {
    children: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
    align: PropTypes.string,
    style: PropTypes.object,
    data: PropTypes.array
}

export default {
    table: Table,
    column: Column
}